﻿package src {
	
	import flash.display.LoaderInfo;
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.filters.BitmapFilterQuality;
	import flash.filters.DropShadowFilter;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.system.System;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	import flash.text.TextFormatAlign;
	
	/**
	 * Support class for the Viewer. Helps to break the code apart.
	 * @author Indrek Tamming
	 */
	public class ViewerEngine extends MovieClip {
		
		/**
		 * An array to hold MotifMatches objects ( data from .matches.shift files )
		 */
		public var motifMatchesArray:Array;
		
		/**
		 * Represents information about sequence data ( data from .fa file )
		 */
		public var seqData:SequenceData;
		
		// For loading data:
		
		/**
		 * Array of .mx files locations
		 */
		private var mxFilesLocations:Array;
		
		/**
		 * Array of track files locations
		 */
		private var tabFilesLocations:Array;
		
		/**
		 * String to hold sequence data location
		 */
		private var seqDataLocation:String;
		
		// For preloading:
		
		/**
		 * Array of Motif objects
		 */
		private var motifs:Array;
		
		/**
		 * Motif parameters
		 */
		private var motifParameters:Array;
		
		/**
		 * Arrays indexer for loading
		 */
		private var mxFilesLocIndexer:int = 0;
		
		/**
		 * indexer
		 */
		private var tabFilesLocIndexer:int = 0;
		
		/**
		 * true if all motifs are loaded
		 */
		private var motifsLoaded:Boolean = false;
		
		/**
		 * indexer
		 */
		private var motifArrayIndexer:int = 0;
		
		/**
		 * true if .matches files are loaded
		 */
		private var matchesLoaded:Boolean = false;
		
		/**
		 * indexer
		 */
		private var matchesArrayIndexer:int = 0;
		
		/**
		 * true if sequence.fa is loaded
		 */
		private var seqDataLoaded:Boolean = false;
		
		/**
		 * true if all tracks are loaded
		 */
		private var conservDataLoaded:Boolean = false;
		
		// Conservation parameters:
		
		/**
		 * Array for tracks parameter objects
		 */
		public var conservationParams:Array;
		
		/**
		 * Textfield for preloading information
		 */
		private var loadingField:TextField = new TextField();
		
		// Zoom level constants:
		
		/**
		 * view's height
		 */
		public var viewHeight:Number = 600;
		
		/**
		 * view's width
		 */
		public var viewWidth:Number = 750;
		
		/**
		 * letter size zoom level 1
		 */
		private var letterSize1:int = 10;
		
		/**
		 * letter size zoom level 2
		 */
		private var letterSize2:int = 5;
		
		/**
		 * letter size zoom level 3
		 */
		private var letterSize3:int = 1;
		
		/**
		 * letter size zoom level 4
		 */
		private var letterSize4:Number = 0.1;
		
		/**
		 * An array to hold different colors for matches
		 */
		public var matchesColors:Array = [
			0xFF0000,
			0x00FF00,
			0x0000FF,
			0x00FFFF,
			0xFF00FF,
			0xFF9900,
			0x669900,
			0x9933FF,
			0x006633,
			0xCC9933];
		
		/**
		 * Indexes the colors, so they wont get messed up
		 */
		private var colorIndex:int = 0;
		
		/**
		 * Override in Viewer.as
		 * Called at framerate to process all neccessary code for dragging.
		 */
		public function processDragging():void { }
		
		/**
		 * Override in Viewer.as
		 * Called onEnterFrame to check if view or UI needs redraw
		 */
		public function processRedrawing():void { }
		
		/**
		 * Override in Viewer.as
		 * Called when loading is done
		 */
		public function initViewer():void { }
		
		
		public function ViewerEngine():void {
			trace(">>> >> > ViewerEngine started...");
			
			drawPreloader();
			addEventListener(Event.ENTER_FRAME, processLoading);
			
			// Load the FlashVars variables:
			///*
			try {
				var paramObj:Object = LoaderInfo(this.root.loaderInfo).parameters;
				seqDataLocation = paramObj["seqData"];
				mxFilesLocations = String(paramObj["mxFiles"]).split("\n");
				if (mxFilesLocations[mxFilesLocations.length - 1].length < 3) mxFilesLocations.pop();
				tabFilesLocations = String(paramObj["trackFiles"]).split("\n");
				if (tabFilesLocations[tabFilesLocations.length -1].length < 3) tabFilesLocations.pop();
			} catch (error:Error) {
				trace(error.toString());
			}
			//*/
			
			/*
			// Load local variables:
			seqDataLocation = "\data/100seq_300nt"; // the most common
			//seqDataLocation = "\data/100seq_2000nt";
			//seqDataLocation = "\data/1000seq_80nt";
			//seqDataLocation = "\data/1000seq_700nt";
			//seqDataLocation = "\data/1000seq_10000nt"; // the largest
			//seqDataLocation = "\data/1000seq_300nt";
			mxFilesLocations =
				["\data/MA0001.mx orange 5",
				//"\data/MA0002.mx",
				//"\data/MA0003.mx blue",
				//"\data/MA0004.mx",
				"\data/MA0005.mx blue 6",
				//"\data/MA0006.mx blue 0",
				"\data/MA0007.mx red 4",
				//"\data/MA0008.mx",
				"\data/MA0009.mx green 4"];
			tabFilesLocations =
				[seqDataLocation + "/conservation.tab tomato center",
				seqDataLocation + "/conservation.tab"];
			*/
			
			// Now we have locations, start loading with seq data:
			loadSequenceData();
		}
		
		/**
		 * Init the loadingField
		 */
		private function drawPreloader():void {
			loadingField.selectable = false;
			loadingField.autoSize = TextFieldAutoSize.LEFT;
			loadingField.x = 200;
			loadingField.y = 250;
			loadingField.width = 530;
			loadingField.height = 35;
			var format:TextFormat = new TextFormat("Arial", 25, 0, true);
			format.align = TextFormatAlign.CENTER;
			loadingField.defaultTextFormat = format;
			loadingField.text = "Loading starting";
			addChild(loadingField);
			loadingField.filters = [new DropShadowFilter(3, 35, 0, 0.5, 4, 4, 0.5,BitmapFilterQuality.LOW)];
		}
		
		/**
		 * Catch IO errors here
		 * @param	e
		 */
		private function ioError(e:IOErrorEvent):void {
			
			// TODO OR NOT ?
			trace("ioerror");
			
		}
		
		/**
		 * Start loading sequence.fa
		 */
		private function loadSequenceData():void {
			var myTextLoader:URLLoader = new URLLoader();
			myTextLoader.addEventListener(Event.COMPLETE, sequenceDataLoaded);
			myTextLoader.load(new URLRequest(seqDataLocation + "/sequence.fa"));
		}
		
		/**
		 * sequence.fa loaded
		 * @param	e	-	Event.COMPLETE
		 */
		private function sequenceDataLoaded(e:Event):void {
			var _length:int = tabFilesLocations.length;
			conservationParams = new Array(_length);
			
			seqData = new SequenceData(e.target.data, this, conservationParams);
			seqData.mouseEnabled = false;
			seqData.mouseChildren = false;
			seqDataLoaded = true;
			
			// Seq data loaded, now load conservation data:
			loadConservationData();
		}
		
		/**
		 * Load (another) track
		 */
		private function loadConservationData():void {
			var myTextLoader:URLLoader = new URLLoader();
			myTextLoader.addEventListener(Event.COMPLETE, conservationDataLoaded);
			if (tabFilesLocIndexer == tabFilesLocations.length) {
				conservDataLoaded = true;
				
				// Conservation data loaded, now load motif matrixes:
				var _length:int = mxFilesLocations.length;
				motifParameters = new Array(_length);
				motifMatchesArray = new Array(_length);
				motifs = new Array(_length);
				loadMotifMatrixes();
				return;
			}
			var params:Object;
			var _name:Array;
			if (tabFilesLocations[tabFilesLocIndexer].indexOf(" ") < 0) {
				params = new Object();
				params.color = 0xFF0000;
				params.style = "upper";
				params.hidden = false;
				_name = String(tabFilesLocations[tabFilesLocIndexer]).split("/");
				params.trackName = _name[_name.length-1];
				conservationParams[tabFilesLocIndexer] = params;
				
				myTextLoader.load(new URLRequest(tabFilesLocations[tabFilesLocIndexer]));
				return;
			}
			var location:Array = tabFilesLocations[tabFilesLocIndexer].split(" ");
			params = new Object();
			params.color = procColorParam(location[1]);
			//params.style = String(location[2]).indexOf("center") >= 0 ? "center" : "upper";
			
			if (String(location[2]).indexOf("upper") >= 0) params.style = "upper";
			else if (String(location[2]).indexOf("lower") >= 0) params.style = "lower";
			else params.style = "center";
			
			params.hidden = false;
			_name = String(location[0]).split("/");
			params.trackName = _name[_name.length-1];
			conservationParams[tabFilesLocIndexer] = params;
			
			myTextLoader.load(new URLRequest(location[0]));
		}
		
		/**
		 * Track loaded
		 * @param	e	-	complete event
		 */
		private function conservationDataLoaded(e:Event):void {
			var consData:ConservationData = new ConservationData(e.target.data);
			seqData.addConservation(consData);
			tabFilesLocIndexer++;
			loadConservationData(); // recursively continue
		}
		
		/**
		 * Load (another .mx file)
		 */
		private function loadMotifMatrixes():void {
			var myTextLoader:URLLoader = new URLLoader();
			myTextLoader.addEventListener(Event.COMPLETE, motifMatrixLoaded);
			if (mxFilesLocIndexer == mxFilesLocations.length) {
				motifsLoaded = true;
				
				// Matrixes loaded, now load matches:
				loadMotifMatches();
				return;
			}
			if (mxFilesLocations[mxFilesLocIndexer].indexOf(" ") < 0) {
				myTextLoader.load(new URLRequest(mxFilesLocations[mxFilesLocIndexer]));
				return;
			}
			var location:Array = mxFilesLocations[mxFilesLocIndexer].split(" ");
			var obj:Object = new Object();
			obj.color = location[1];
			obj.filter = location[2];
			motifParameters[mxFilesLocIndexer] = obj;
			myTextLoader.load(new URLRequest(location[0]));
		}
		
		/**
		 * .mx file loaded
		 * @param	e	-	 complete event
		 */
		private function motifMatrixLoaded(e:Event):void {
			var data:String = String(e.target.data);
			if (!(data.length > 0) || data == null) return;
			motifs[mxFilesLocIndexer] = new Motif(data);
			mxFilesLocIndexer++;
			loadMotifMatrixes(); // recursive
		}
		
		/**
		 * Load .matches files
		 */
		private function loadMotifMatches():void {
			var myTextLoader:URLLoader = new URLLoader();
			myTextLoader.addEventListener(Event.COMPLETE, motifMatchesLoaded);
			if (matchesArrayIndexer == mxFilesLocations.length) {
				matchesLoaded = true;
				// now all data is loaded
				return;
			}
			
			if (motifs[matchesArrayIndexer].getName() == "html>") { // WTF??
				matchesLoaded = true;
				motifs.pop();
				return; 
			}
			
			//if (seqDataLocation.indexOf("10000nt") > 0) {
				myTextLoader.load(new URLRequest(seqDataLocation + "/" + motifs[matchesArrayIndexer].getName() + ".matches"));
			//}
			//else {
				//myTextLoader.load(new URLRequest(seqDataLocation + "/" + motifs[matchesArrayIndexer].getName() + ".matches.shift"));
			//}
		}
		
		/**
		 * Matches file loaded
		 * @param	e	-	 Event.COMPLETE
		 */
		private function motifMatchesLoaded(e:Event):void {
			var data:String = String(e.target.data);
			if (!(data.length > 0) || data == null) return;
			var motifMatches:MotifMatches = new MotifMatches(data);
			motifMatchesArray[matchesArrayIndexer] = motifMatches;
			matchesArrayIndexer++;
			loadMotifMatches();
		}
		
		/**
		 * Checks if lodaing is done
		 * @param	e	-	the onEnterFrame event
		 */
		private function processLoading(e:Event):void {
			if (!allLoaded()) return;
			loadingDone();
		}
		
		/**
		 * @return true if all data is loaded
		 */
		private function allLoaded():Boolean {
			var bytesUsed:String = "\n" + String(System.totalMemory) + "bytes of memory used";
			if (!seqDataLoaded) {
				showLoadingFieldText("Loading sequence.fa"+bytesUsed);
				return false;
			}
			if (!conservDataLoaded) {
				showLoadingFieldText("Loading conservation tracks "+tabFilesLocIndexer+bytesUsed);
				return false;
			}
			if (!motifsLoaded) {
				showLoadingFieldText("Loading .mx file(s) "+mxFilesLocIndexer+bytesUsed);
				return false;
			}
			if (!matchesLoaded) {
				showLoadingFieldText("Loading .matches file(s) "+matchesArrayIndexer+bytesUsed);
				return false;
			}
			trace("all data loaded");
			return true;
		}
		
		/**
		 * Initializes the Viewer and the process loop
		 */
		private function loadingDone():void {
			removeEventListener(Event.ENTER_FRAME, processLoading);
			removeChild(loadingField);
			
			// To doublecheck:
			if (motifs[motifs.length - 1] == null) motifs.pop();
			if (motifMatchesArray[motifMatchesArray.length - 1] == null) motifMatchesArray.pop();
			
			addMotifParameters();
			addEventListener(Event.ENTER_FRAME, process);
			System.gc();
			initViewer();
		}
		
		/**
		 * Add neccessary parameters to matches objects
		 */
		private function addMotifParameters():void {
			var _length:int = motifMatchesArray.length;
			for (var i:int = 0; i < _length; i++) {
				var matches:MotifMatches = motifMatchesArray[i];
				if (matches == null) continue;
				matches.setMotif(motifs[i]);
				var obj:Object = motifParameters[i];
				if (obj == null) continue;
				if (obj != null && obj.color != null) {
					var c:uint = procColorParam(obj.color);
					if(c == 0 && String(obj.color).toLowerCase() != "black") matches.setColor(matchesColors[colorIndex++]);
					else matches.setColor(c);
					if (obj.filter != null) matches.setFilter(obj.filter);
				}
				else {
					matches.setColor(matchesColors[colorIndex++]);
				}
			}
		}
		
		/**
		 * Change input parameters to color values:
		 */
		private function procColorParam(param:String):uint {
			if (param.charAt(0) == "#") return uint("0x" + param.substring(1));
			param = param.toLowerCase();
			switch(param) {
				case "aqua": return 0x00FFFF; break;
				case "black": return 0x000000; break;
				case "blue": return 0x0000FF; break;
				case "blueviolet": return 0x8A2BE2; break;	 
				case "brown": return 0xA52A2A; break;
				case "chocolate": return 0xD2691E; break;
				case "cyan": return 0x00FFFF; break;
				case "darkblue": return 0x00008B; break;
				case "darkcyan": return 0x008B8B; break;
				case "darkgray": return 0xA9A9A9; break;
				case "darkgreen": return 0x006400; break;
				case "darkmagenta": return 0x8B008B; break;
				case "darkolivegreen": return 0x556B2F; break;
				case "darkorange": return 0xFF8C00; break;
				case "darkred": return 0x8B0000; break;
				case "darkturquoise": return 0x00CED1; break;
				case "darkviolet": return 0x9400D3; break;
				case "deeppink": return 0xFF1493; break;
				case "deepskyblue": return 0x00BFFF; break;
				case "gold": return 0xFFD700; break;
				case "gray": return 0x808080; break;
				case "green": return 0x008000; break;
				case "greenyellow": return 0xADFF2F; break;	 
				case "indigo": return 0x4B0082; break;
				case "lightblue": return 0xADD8E6; break;
				case "lightcyan": return 0xE0FFFF; break;
				case "lightgrey": return 0xD3D3D3; break;
				case "lightgreen": return 0x90EE90; break;
				case "lightpink": return 0xFFB6C1; break;
				case "lightskyblue": return 0x87CEFA; break;
				case "lightsteelblue": return 0xB0C4DE; break;
				case "lightyellow": return 0xFFFFE0; break;
				case "limegreen": return 0x32CD32; break;
				case "magenta": return 0xFF00FF; break;
				case "maroon": return 0x800000; break;
				case "mediumblue": return 0x0000CD; break;
				case "mediumpurple": return 0x9370D8; break;
				case "mediumseagreen": return 0x3CB371; break;
				case "mediumspringgreen": return 0x00FA9A; break;
				case "mediumturquoise": return 0x48D1CC; break;
				case "mediumvioletred": return 0xC71585; break;
				case "midnightblue": return 0x191970; break;
				case "navy": return 0x000080; break;
				case "olive": return 0x808000; break;
				case "orange": return 0xFFA500; break;
				case "orangered": return 0xFF4500; break;
				case "pink": return 0xFFC0CB; break;
				case "purple": return 0x800080; break;
				case "red": return 0xFF0000; break;
				case "seagreen": return 0x2E8B57; break;
				case "silver": return 0xC0C0C0; break;
				case "skyblue": return 0x87CEEB; break;
				case "springgreen": return 0x00FF7F; break;
				case "steelblue": return 0x4682B4; break;
				case "tomato": return 0xFF6347; break;
				case "turquoise": return 0x40E0D0; break;
				case "violet": return 0xEE82EE; break;
				case "yellow": return 0xFFFF00; break;
				case "yellowgreen": return 0x9ACD32; break;
				default: return 0; break;
			}
			return 0;
		}
		
		/**
		 * All the looping action here
		 * @param	e	-	the onEnterFrame event
		 */
		private function process(e:Event):void {
			processDragging();
			processRedrawing();
		}
		
		/**
		 * Shows text while preloading
		 * @param	fieldText	-	the text to show
		 */
		public function showLoadingFieldText(fieldText:String):void {
			loadingField.text = fieldText;
		}
		
		// Zoom level constants getters and setters:
		/**
		 * Get letter size according to zoom level
		 * @param	zoomLevel	-	zoom level
		 * @return	-	the letter size in pixels
		 */
		public function getLetterSize(zoomLevel:int):Number {
			switch(zoomLevel) {
				case 1: return letterSize1; break;
				case 2: return letterSize2; break;
				case 3: return letterSize3; break;
				case 4: return letterSize4; break;
				default: break;
			}
			return 0;
		}
		
		/**
		 * Set letter size
		 * @param	zoomLevel
		 * @param	newSize
		 */
		public function setLetterSize(zoomLevel:int, newSize:Number):void {
			switch(zoomLevel) {
				case 1: letterSize1 = newSize; break;
				case 2: letterSize2 = newSize; break;
				case 3: letterSize3 = newSize; break;
				case 4: letterSize4 = newSize; break;
				default: break;
			}
		}
		
		/**
		 * Get space between two rows
		 * @param	zoomLevel
		 * @return
		 */
		public function getSpaceBetweenRows(zoomLevel:int):Number {
			switch(zoomLevel) {
				case 1: return 40; break;
				case 2: return 35; break;
				case 3: return 25; break;
				case 4: return 20; break;
				default: break;
			}
			return 0;
		}
		
		/**
		 * Get the number of rows on view
		 * @param	zoomLevel
		 * @return
		 */
		public function getNumberOfRowsOnView(zoomLevel:int):int {
			return viewHeight / getSpaceBetweenRows(zoomLevel) - 1;
		}
		
		/**
		 * Get the number of letters in a row
		 * @param	zoomLevel
		 * @return
		 */
		public function getNumberOfLettersInRow(zoomLevel:int):int {
			switch(zoomLevel) {
				case 1: return viewWidth / letterSize1 - 1; break;
				case 2: return viewWidth / letterSize2 - 2; break;
				case 3: return (viewWidth - 10) / letterSize3; break;
				case 4: return (viewWidth - 20) / letterSize4; break;
				default: break;
			}
			return 0;
		}
		
		/**
		 * Trim the whtspace of a String
		 * @param	inputStr
		 * @return	-	 trimmed string
		 */
		public function trim(inputStr:String):String {
			var temp:String = inputStr;
			var obj:RegExp = /^(\s*)([\W\w]*)(\b\s*$)/;
			if (obj.test(temp)) temp = temp.replace(obj, '$2');
			return temp;
		}
		
	}
	
}